{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基于物品的协同过滤"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 导入工具包"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# load数据（用户和物品索引，以及倒排表）\n",
    "import _pickle as cPickle\n",
    "\n",
    "# 稀疏矩阵，打分表\n",
    "import scipy.io as sio\n",
    "import os\n",
    "\n",
    "# 距离\n",
    "import scipy.spatial.distance as ssd\n",
    "import time"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 读入数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 用户和item的索引\n",
    "users_index = cPickle.load(open(\"./out/users_index.pkl\", 'rb'))\n",
    "items_index = cPickle.load(open(\"./out/items_index.pkl\", 'rb'))\n",
    "\n",
    "n_users = len(users_index)\n",
    "n_items = len(items_index)\n",
    "    \n",
    "# 倒排表\n",
    "# 每个用户打过分的电影\n",
    "user_items = cPickle.load(open(\"./out/user_items.pkl\", 'rb'))\n",
    "# 对每个电影打过分的事用户\n",
    "item_users = cPickle.load(open(\"./out/item_users.pkl\", 'rb'))\n",
    "\n",
    "# 用户-物品关系矩阵R\n",
    "user_item_scores = sio.mmread(\"./out/user_item_scores\")\n",
    "user_item_scores = user_item_scores.tocsr()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 计算每个用户的平均打分"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "users_mu = np.zeros(n_users)\n",
    "for u in range(n_users):  \n",
    "    r_acc = 0.0\n",
    "    n_user_items = 0\n",
    "    \n",
    "    for i in user_items[u]:\n",
    "        # 用户打过分的item\n",
    "        r_acc += user_item_scores[u,i]\n",
    "        n_user_items += 1\n",
    "    \n",
    "    users_mu[u] = 1.0 * r_acc / n_user_items\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 计算两个item之间的相似度"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def item_similarity(iid1, iid2):\n",
    "    \"\"\"\n",
    "    根据对iid1和iid2都打过分的user, 来计算iid1和iid2的相似度\n",
    "    :param iid1: Item 1号\n",
    "    :param iid2: Item 2号\n",
    "    :return: [Item 1号] 和 [Item 2号] 的相似度\n",
    "    \"\"\"\n",
    "    \n",
    "    # 对iid1和iid2都打过分的user\n",
    "    su = {}\n",
    "    for user in item_users[iid1]:\n",
    "        if user in item_users[iid2]:\n",
    "            su[user] = 1\n",
    "        \n",
    "    n = len(su)\n",
    "    if n == 0:\n",
    "        similarity = 0  \n",
    "        return similarity  \n",
    "\n",
    "    # iid1的有效打分(减去用户的平均打分)\n",
    "    s1 = np.array([user_item_scores[user,iid1] - users_mu[user] for user in su])\n",
    "        \n",
    "    # iid2的有效打分(减去用户的平均打分)\n",
    "    s2 = np.array([user_item_scores[user,iid2] - users_mu[user] for user in su])  \n",
    "    \n",
    "    similarity = 1 - ssd.cosine(s1, s2) \n",
    "    if np.isnan(similarity):\n",
    "        similarity = 0.0\n",
    "    \n",
    "    return similarity\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 预计算好所有item之间的相似性\n",
    "对item比较少、Item比较固定的系统适用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "开始计算1650个Item两两之间的相似度\ni=0\n处理到item_index=0, 耗时0.0240秒\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Python37\\lib\\site-packages\\scipy\\spatial\\distance.py:720: RuntimeWarning: invalid value encountered in double_scalars\n  dist = 1.0 - uv / np.sqrt(uu * vv)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=100\n处理到item_index=100, 耗时147.7364秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=200\n处理到item_index=200, 耗时258.5258秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=300\n处理到item_index=300, 耗时350.9221秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=400\n处理到item_index=400, 耗时423.8712秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=500\n处理到item_index=500, 耗时469.6529秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=600\n处理到item_index=600, 耗时509.7542秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=700\n处理到item_index=700, 耗时527.4572秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=800\n处理到item_index=800, 耗时545.2022秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=900\n处理到item_index=900, 耗时556.5208秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=1000\n处理到item_index=1000, 耗时564.0233秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=1100\n处理到item_index=1100, 耗时568.6915秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=1200\n处理到item_index=1200, 耗时571.4437秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=1300\n处理到item_index=1300, 耗时572.9158秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=1400\n处理到item_index=1400, 耗时573.9998秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=1500\n处理到item_index=1500, 耗时574.6429秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i=1600\n处理到item_index=1600, 耗时574.8949秒\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "计算1650个Item两两之间的相似度共计耗时575.1729秒\nOver!\n"
     ]
    }
   ],
   "source": [
    "time_start = time.time()\n",
    "items_similarity_matrix = np.matrix(np.zeros(shape=(n_items, n_items)), float)\n",
    "\n",
    "print(\"开始计算{}个Item两两之间的相似度\".format(n_items))\n",
    "\n",
    "each_time_start = time_start\n",
    "for i in range(n_items):\n",
    "    items_similarity_matrix[i, i] = 1.0\n",
    "    \n",
    "    # 打印进度条\n",
    "    if i % 100 == 0:\n",
    "        each_time_tick = time.time()\n",
    "        each_cost_time = each_time_tick - each_time_start\n",
    "        each_time_start = time_start\n",
    "        print(\"处理到item_index={}, 耗时{:.4f}秒\".format(\n",
    "            i, each_cost_time\n",
    "        ))\n",
    "\n",
    "    for j in range(i + 1, n_items):\n",
    "        similarity = item_similarity(i, j)\n",
    "        items_similarity_matrix[i, j] = similarity\n",
    "        items_similarity_matrix[j, i] = similarity\n",
    "        \n",
    "cPickle.dump(items_similarity_matrix, open(\"./out/items_similarity.pkl\", 'wb')) \n",
    "\n",
    "time_end = time.time()\n",
    "total_cost_time = time_end - time_start\n",
    "print(\"计算{}个Item两两之间的相似度共计耗时{:.4f}秒\".format(\n",
    "    n_items, total_cost_time\n",
    "))\n",
    "\n",
    "print(\"Over!\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 预测用户对item的预测打分1\n",
    "根据User打过分的所有Item，来预测User对当前目标Item的打分\n",
    "RMSE为3.X, 很多预测打分为负值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def Item_CF_pred1(uid, iid):\n",
    "    \"\"\"\n",
    "    根据User打过分的所有Item,\n",
    "    来预测User对目标Item的打分\n",
    "    :param uid: 目标User的index\n",
    "    :param iid: 目标Item的index\n",
    "    :return: User对目标Item的打分\n",
    "    \"\"\"\n",
    "    \n",
    "    weighted_total_sim = 0.0\n",
    "    total_sim = 0.0\n",
    "    \n",
    "    for item_id in user_items[uid]:\n",
    "        each_item_sim = items_similarity_matrix[item_id, iid]\n",
    "        \n",
    "        if each_item_sim != 0:\n",
    "            each_item_score = user_item_scores[uid, item_id]\n",
    "            weighted_total_sim += each_item_sim * each_item_score\n",
    "            total_sim += np.abs(each_item_sim)                 \n",
    "        \n",
    "    if total_sim != 0:\n",
    "        score = weighted_total_sim / total_sim\n",
    "    else:\n",
    "        score = users_mu[uid]\n",
    "    \n",
    "    if score < 0:\n",
    "        score = 0.0\n",
    "    \n",
    "    return score\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 预测用户对item的预测打分2\n",
    "根据User打过分的Item中，与当前Item最相似的若干个Item, 来预测User对目标Item的打分\n",
    "RMSE为1.13\n",
    "PR和覆盖度性能最好"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def Item_CF_pred2(uid, iid, n_Knns): \n",
    "    \"\"\"\n",
    "    根据User打过分的Item中, 与目标Item最相似的若干个Item,\n",
    "    来预测User对目标Item的打分\n",
    "    :param uid: 目标User\n",
    "    :param iid: 目标Item的index\n",
    "    :param n_Knns: 所参考的其他Item的个数\n",
    "    :return: User对目标Item的打分\n",
    "    \"\"\"\n",
    "    \n",
    "    # 目标Item与所有其他Item的相似度\n",
    "    cur_items_similarity = np.array(items_similarity_matrix[iid, :]).flatten()\n",
    "    cur_items_similarity = list(cur_items_similarity)\n",
    "    sim_index_list = [(e, i) for i, e in enumerate(cur_items_similarity)]\n",
    "    sorted_sim_index_list = sorted(sim_index_list, reverse=True)\n",
    "    \n",
    "    # 对于所参考的其他Item, 累积其与目标Item的相似度加权和\n",
    "    weighted_total_sim = 0.0\n",
    "    # 对于所参考的其他Item, 累积其与目标Item的相似度之和\n",
    "    total_sim = 0.0\n",
    "    # 对于所参考的其他Item, 累积其个数\n",
    "    top_n_items = 0\n",
    "    \n",
    "    for each_item_sim, each_item_index in sorted_sim_index_list:\n",
    "        if each_item_index not in user_items[uid]:\n",
    "            continue\n",
    "\n",
    "        if each_item_sim != 0:\n",
    "            each_item_score = user_item_scores[uid, each_item_index]\n",
    "            weighted_total_sim += each_item_sim * each_item_score\n",
    "            total_sim += np.abs(each_item_sim)  \n",
    "        \n",
    "        top_n_items += 1\n",
    "        if top_n_items >= n_Knns:\n",
    "            break\n",
    "        \n",
    "    if total_sim != 0:  \n",
    "        score = weighted_total_sim / total_sim\n",
    "    else:\n",
    "        score = users_mu[uid]\n",
    "    \n",
    "    if score < 0:\n",
    "        score = 0.0\n",
    "    \n",
    "    return score\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 预测用户对item的预测打分3\n",
    "根据与目标Item最相似的若干个Item(不管User是否对其打过分), 来预测User对目标Item的打分\n",
    "RMSE为1.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def Item_CF_pred3(uid, iid, n_Knns):\n",
    "    \"\"\"\n",
    "    根据与目标Item最相似的若干个Item,\n",
    "    来预测User对当前目标Item的打分\n",
    "    :param uid: 当前User的index\n",
    "    :param iid: 当前Item的index\n",
    "    :param n_Knns: 参考的其他Item个数\n",
    "    :return: 预测当前User对目标Item的打分\n",
    "    \"\"\"\n",
    "    \n",
    "    cur_items_similarity = np.array(items_similarity_matrix[iid, :]).flatten()\n",
    "    cur_items_similarity = list(cur_items_similarity)\n",
    "    sim_index_list = [(e, i) for i, e in enumerate(cur_items_similarity)]\n",
    "    sorted_sim_index_list = sorted(sim_index_list, reverse=True)[0: n_Knns]\n",
    "    \n",
    "    weighted_total_sim = 0.0  \n",
    "    total_sim = 0.0\n",
    "    \n",
    "    for each_item_sim, each_sim_index in sorted_sim_index_list:\n",
    "        if each_sim_index not in user_items[uid]:\n",
    "            continue\n",
    "        \n",
    "        if each_item_sim != 0:\n",
    "            each_item_score = user_item_scores[uid, each_sim_index]\n",
    "            weighted_total_sim += each_item_sim * each_item_score\n",
    "            total_sim += np.abs(each_item_sim)  \n",
    "        \n",
    "    if total_sim != 0: \n",
    "        score = weighted_total_sim / total_sim\n",
    "    else:  \n",
    "        score = users_mu[uid]\n",
    "    \n",
    "    if score < 0:\n",
    "        score = 0.0\n",
    "    \n",
    "    return score\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 对给定用户，推荐物品/计算打分\n",
    "不同的推荐算法，只是预测打分函数不同，\n",
    "user_items_scores[i] = User_CF_pred(cur_user_id, i)  #预测打分\n",
    "\n",
    "如User_CF_pred, Item_CF_pred, svd_CF_pred,...\n",
    "甚至基于内容的推荐也是一样。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "\n",
    "def recommend(user):\n",
    "    \"\"\"\n",
    "    返回推荐给User的Item列表和打分信息\n",
    "    :param user:        目标User的userId\n",
    "    :return:    推荐结果列表 \n",
    "    \"\"\"\n",
    "    \n",
    "    cur_user_index = users_index[user]\n",
    "    cur_user_items = user_items[cur_user_index]\n",
    "    user_items_scores = np.zeros(n_items)\n",
    "\n",
    "    for each_item_index in range(n_items): \n",
    "        if each_item_index not in cur_user_items:\n",
    "            user_items_scores[each_item_index] = Item_CF_pred2(cur_user_index, \n",
    "                                                               each_item_index, \n",
    "                                                               10)\n",
    "    \n",
    "    score_index_list = [(e, i) for i, e in enumerate(list(user_items_scores))]\n",
    "    sorted_score_index_list = sorted(score_index_list, reverse=True)\n",
    "    \n",
    "    columns = ['item_id', 'score']\n",
    "    df = pd.DataFrame(columns=columns)\n",
    "    \n",
    "    for each_score, each_item_index in sorted_score_index_list:\n",
    "        if np.isnan(each_score):\n",
    "            continue\n",
    "        \n",
    "        if each_item_index in cur_user_items:\n",
    "            continue\n",
    "        \n",
    "        # 根据value值(Item.index)寻找key值(Item.id)\n",
    "        each_item_key_index = list(items_index.values()).index(each_item_index)\n",
    "        each_item_id = list(items_index.keys())[each_item_key_index]\n",
    "        df.loc[len(df)] = [each_item_id, each_score]\n",
    "\n",
    "    return df\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 读取测试数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "triplet_cols = ['user_id', 'item_id', 'rating', 'timestamp'] \n",
    "\n",
    "test_data_path = './data/u1.test'\n",
    "df_triplet_test = pd.read_csv(test_data_path, \n",
    "                              sep='\\t', \n",
    "                              names=triplet_cols, \n",
    "                              encoding='latin-1')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 测试，并计算评价指标\n",
    "PR、覆盖度、RMSE\n",
    "这部分代码所有的推荐算法相同\n",
    "\n",
    "令系统的用户集合为 U， R(u) 是根据用户在训练集上的行为给用户作出的推荐列表，而 T(u) 是用户在测试集上的行为列表。那么推荐结果的准确率定义为：\n",
    "$$\n",
    "Precision=\\frac{\\sum_{u\\in U}|R(u)\\cap T(u)|}{\\sum_{u\\in U}|R(u)|}\n",
    "$$\n",
    "推荐结果的召回率定义为：\n",
    "$$\n",
    "Recall=\\frac{\\sum_{u\\in U}|R(u)\\cap T(u)|}{\\sum_{u\\in U}|T(u)|}\n",
    "$$\n",
    "\n",
    "推荐系统的覆盖率为：\n",
    "$$\n",
    "Coverage=\\frac{\\sum_{u\\in U}|R(u)|}{|I|}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def recommend_users(user_ids):\n",
    "    \"\"\"\n",
    "    为指定一批User推荐Item, 并返回推荐结果\n",
    "    :param user_ids: userId列表\n",
    "    :return: \n",
    "    \"\"\"\n",
    "    userid_rec_results = dict()\n",
    "    \n",
    "    time_start = time.time()\n",
    "    each_time_start = time_start\n",
    "    count = 0\n",
    "    \n",
    "    print(\"开始对{}个User计算推荐结果\".format(len(user_ids)))\n",
    "    for user_id in user_ids:\n",
    "        count = count + 1\n",
    "        if user_id not in users_index:\n",
    "            print(\"{} is a new user.\".format(user_id))\n",
    "            continue\n",
    "        \n",
    "        each_rec_items = recommend(user_id)\n",
    "        userid_rec_results[user_id] = each_rec_items\n",
    "        \n",
    "        if count % 100 == 0:\n",
    "            each_time_tick = time.time()\n",
    "            each_cost_time = each_time_tick - each_time_start\n",
    "            each_time_start = each_time_tick\n",
    "            print(\"user_index:{}, each_cost_time:{}\".format(\n",
    "                    count - 1, each_cost_time))\n",
    "            \n",
    "    time_end = time.time()\n",
    "    total_cost_time = time_end - time_start\n",
    "    print(\"完成{}个User的推荐, 共计耗时{}秒\".format(\n",
    "        count, total_cost_time\n",
    "    ))\n",
    "        \n",
    "    return userid_rec_results\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "开始对459个User计算推荐结果\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_index:99, each_cost_time:476.13823342323303\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_index:199, each_cost_time:479.92145013809204\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_index:299, each_cost_time:479.76344084739685\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_index:399, each_cost_time:469.2458393573761\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "完成459个User的推荐, 共计耗时2173.505317211151秒\n"
     ]
    }
   ],
   "source": [
    "# 测试集中的user_id集合\n",
    "unique_users_test = df_triplet_test['user_id'].unique()\n",
    "total_rec_results = recommend_users(unique_users_test)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate(userid_rec_results, n_rec_items):\n",
    "    \"\"\"\n",
    "    为每个User截取若干条推荐结果，并衡量全体User的推荐效果\n",
    "    :param userid_rec_results:  推荐结果<userId -- 该user被推荐的全部Item>\n",
    "    :param n_rec_items:         每个所需截取的推荐结果的数量\n",
    "    :return: \n",
    "    \"\"\"\n",
    "    n_hits = 0              # TP\n",
    "    n_total_rec_items = 0   # TP+FP\n",
    "    n_total_test_items = 0  # TP+FN\n",
    "    all_rec_items = set()   # 所有User被推荐的Item的汇总\n",
    "    rss_test = 0.0          # 所有User的预测残差平方和\n",
    "\n",
    "    for user_id, rec_items in userid_rec_results.items():\n",
    "        user_records_test = df_triplet_test[df_triplet_test.user_id == user_id]\n",
    "        \n",
    "        # 累积TP和汇总所有User被推荐的指定数量的Item\n",
    "        for i in range(n_rec_items):\n",
    "            item_id = rec_items.iloc[i]['item_id']\n",
    "            if item_id in user_records_test['item_id'].values:\n",
    "                n_hits = n_hits + 1\n",
    "            all_rec_items.add(item_id)\n",
    "\n",
    "        # 累积预测评分残差平方和\n",
    "        for i in range(user_records_test.shape[0]):\n",
    "            item = user_records_test.iloc[i]['item_id']\n",
    "            score = user_records_test.iloc[i]['rating']\n",
    "            \n",
    "            df1 = rec_items[rec_items.item_id == item]\n",
    "            if df1.shape[0] == 0:\n",
    "                print(\"{} is a new item.\".format(item))\n",
    "                continue\n",
    "            \n",
    "            pred_score = df1['score'].values[0]\n",
    "            rss_test = rss_test + (pred_score - score)**2\n",
    "    \n",
    "        # 推荐的item总数(TP+FP)\n",
    "        n_total_rec_items = n_total_rec_items + n_rec_items\n",
    "    \n",
    "        # 真实item的总数(TP+FN)\n",
    "        n_total_test_items = n_total_test_items + user_records_test.shape[0]\n",
    "        \n",
    "    # Precision & Recall\n",
    "    precision = n_hits / (1.0 * n_total_rec_items)\n",
    "    recall = n_hits / (1.0 * n_total_test_items)\n",
    "    \n",
    "    # 覆盖度：推荐商品占总需要推荐商品的比例\n",
    "    coverage = len(all_rec_items) / (1.0 * n_items)\n",
    "    \n",
    "    # 打分的均方误差\n",
    "    rmse = np.sqrt(rss_test / df_triplet_test.shape[0])\n",
    "    \n",
    "    print(\"n_rec_items={}时:\\n \\tprecision={}\\n \"\n",
    "          \"\\trecall={}\\n \\tcoverage={}\\n \\trmse={}\".format(n_rec_items, \n",
    "                                                           precision, \n",
    "                                                           recall, \n",
    "                                                           coverage, \n",
    "                                                           rmse))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n推荐10个电影时的结果:\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "599 is a new item.\n711 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "814 is a new item.\n830 is a new item.\n852 is a new item.\n857 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1156 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1236 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1309 is a new item.\n1310 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1320 is a new item.\n1343 is a new item.\n1348 is a new item.\n1364 is a new item.\n1373 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1457 is a new item.\n1458 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1492 is a new item.\n1493 is a new item.\n1498 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1505 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1520 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1533 is a new item.\n1536 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1543 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1557 is a new item.\n1561 is a new item.\n1562 is a new item.\n1563 is a new item.\n1565 is a new item.\n1582 is a new item.\n1586 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n_rec_items=10时:\n \tprecision=0.06274509803921569\n \trecall=0.0144\n \tcoverage=0.48727272727272725\n \trmse=1.1327763491486122\n\n\n推荐20个电影时的结果\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "599 is a new item.\n711 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "814 is a new item.\n830 is a new item.\n852 is a new item.\n857 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1156 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1236 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1309 is a new item.\n1310 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1320 is a new item.\n1343 is a new item.\n1348 is a new item.\n1364 is a new item.\n1373 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1457 is a new item.\n1458 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1492 is a new item.\n1493 is a new item.\n1498 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1505 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1520 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1533 is a new item.\n1536 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1543 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1557 is a new item.\n1561 is a new item.\n1562 is a new item.\n1563 is a new item.\n1565 is a new item.\n1582 is a new item.\n1586 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n_rec_items=20时:\n \tprecision=0.0659041394335512\n \trecall=0.03025\n \tcoverage=0.7260606060606061\n \trmse=1.1327763491486122\n"
     ]
    }
   ],
   "source": [
    "# 分别计算推荐10个电影和20个电影时的precision/recall/coverage/rmse\n",
    "print(\"\\n推荐10个电影时的结果:\")\n",
    "evaluate(total_rec_results, 10)\n",
    "\n",
    "print(\"\\n\\n推荐20个电影时的结果\")\n",
    "evaluate(total_rec_results, 20)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
